﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Diagnostics;
using System.Windows.Forms;
using Microsoft.SqlServer.Management.Smo;
using View = Microsoft.SqlServer.Management.Smo.View;
using System.Reflection;
using System.Runtime.InteropServices;

namespace OneEightySquaredDbScripter
{
    public partial class frmScriptor : Form
    {
        [DllImport("user32.dll")]
        private static extern int SendMessage(IntPtr hWnd, int wMsg, int wParam, int lParam);

        private const Single SCROLL_INTERVAL = 20;

        private enum MoveType
        {
            Table,
            Sproc,
            View
        }

        #region FIELDS
        delegate    void ObjectScriptedDelegate();
        private     DbScriptingEngine   _scriptingEngine;
        private     ScriptingLayout     _scriptingLayout;
        private     bool                _ignore             = false;
        private TreeNode _nodeToMove = null;
        private TreeNode _groupToMoveFrom = null;
        private MoveType _mt = MoveType.Table;
        
        //constants
        private const string ROOT_NODE_TAG = "ROOT";
        private const string UNASSIGNED_NODE_TAG = "Unassigned";
        private const string _DEFAULT_OUTPUT_DIR = "Output Directory";
        
        #endregion

        #region CONSTRUCTOR

        public frmScriptor()
        {
            InitializeComponent();
        }

        #endregion

        #region HELPER FUNCTIONS
       
        private void BuildMenu(TreeNode nodeToMove, TreeNode node, ref ContextMenu cm)
        {
            if (node.Nodes.Count > 0)
            {
                foreach (TreeNode nodeChild in node.Nodes)
                {
                    MenuItem menuChild = new MenuItem(nodeChild.Text);

                    menuChild.Tag = nodeToMove;

                    menuChild.Click += new EventHandler(menuChild_Click);

                    cm.MenuItems.Add(menuChild);
                }
            }
            else
            {
                cm.MenuItems.Add("No Groups exist");
            }
        }

        private TreeNode FindNode(string name, TreeNode parent)
        {            
            foreach (TreeNode node in parent.Nodes)
            {
                if (node.Text == name)
                {
                    return node;
                }

                TreeNode item = FindNode(name, node);

                if (null != item)
                {
                    return item;
                }
            }
            //no node was found so return null
            return null;           
        }

        private void BuildLayoutTree(string movedItemName)
        {
            BuildLayoutTree();
            TreeNode node = FindNode(movedItemName, tvLayout.Nodes[0]);
            if (null != node) tvLayout.SelectedNode = node;
            tvLayout.Select();
        }
       
        private void BuildLayoutTree()
        {
            if (null == _scriptingLayout) return;

            //clear existing tree
            tvLayout.Nodes.Clear();

            TreeNode rootNode = tvLayout.Nodes.Add(_DEFAULT_OUTPUT_DIR);
            rootNode.Tag = ROOT_NODE_TAG;

            TreeNode unassignedGroupNode = rootNode.Nodes.Add(UNASSIGNED_NODE_TAG);
            unassignedGroupNode.Tag = UNASSIGNED_NODE_TAG;

            AddUnGroupedItemsToLayoutTree(unassignedGroupNode);

            if (null == _scriptingLayout.Groups || 0 == _scriptingLayout.Groups.Count) return;

            AddGroupsToLayoutTree(rootNode);

            rootNode.Expand();
        }

        private void AddUnGroupedItemsToLayoutTree(TreeNode parentNode)
        {
            // TABLES
            TreeNode node = parentNode.Nodes.Add(_scriptingLayout.SchemaSubDirectory);
            node.Tag = new ScriptingLayout.MoveDelegate(_scriptingLayout.MoveTableToNewGroup);

            foreach (Table table in _scriptingEngine.Tables)
            {
                if (table.IsSystemObject) continue;

                if (!_scriptingLayout.TableExistsInGroup(table.Name))
                {
                    TreeNode newNode = node.Nodes.Add(table.Name);                    
                }
            }

            // SPROCS
            node = parentNode.Nodes.Add(_scriptingLayout.SprocsSubDirectory);
            node.Tag = new ScriptingLayout.MoveDelegate(_scriptingLayout.MoveSprocToNewGroup);
            foreach (StoredProcedure sproc in _scriptingEngine.StoredProcedures)
            {
                if (sproc.IsSystemObject) continue;

                if (!_scriptingLayout.SprocExistsInGroup(sproc.Name))
                {
                    node.Nodes.Add(sproc.Name);
                }
            }

            // VIEWS
            node = parentNode.Nodes.Add(_scriptingLayout.ViewsSubDirectory);
            node.Tag = new ScriptingLayout.MoveDelegate(_scriptingLayout.MoveViewToNewGroup);
            foreach (View view in _scriptingEngine.Views)
            {
                if (view.IsSystemObject) continue;

                if (!_scriptingLayout.ViewExistsInGroup(view.Name))
                {
                    node.Nodes.Add(view.Name);
                }
            }

            parentNode.Expand();
        }

        private void AddGroupsToLayoutTree(TreeNode parentNode)
        {
            foreach (ScriptingGroup group in _scriptingLayout.Groups)
            {
                // GROUP
                TreeNode groupNode = parentNode.Nodes.Add(group.GroupName);

                // SCHEMA
                TreeNode subNode = groupNode.Nodes.Add(_scriptingLayout.SchemaSubDirectory);
                subNode.Tag = new ScriptingLayout.MoveDelegate(_scriptingLayout.MoveTableToNewGroup);
                foreach (string name in group.Schema)
                {
                    subNode.Nodes.Add(name);
                }

                // SPROCS
                subNode = groupNode.Nodes.Add(_scriptingLayout.SprocsSubDirectory);
                subNode.Tag = new ScriptingLayout.MoveDelegate(_scriptingLayout.MoveSprocToNewGroup);
                foreach (string name in group.Sprocs)
                {
                    subNode.Nodes.Add(name);
                }

                // VIEWS
                subNode = groupNode.Nodes.Add(_scriptingLayout.ViewsSubDirectory);
                subNode.Tag = new ScriptingLayout.MoveDelegate(_scriptingLayout.MoveViewToNewGroup);
                foreach (string name in group.Views)
                {
                    subNode.Nodes.Add(name);
                }

                groupNode.Expand();
            }
        }

        private List<string> GetSprocsToScript()
        {
            List<string> sprocs = new List<string>();
            if (chkAllSprocs.Checked)
            {
                foreach (StoredProcedure sproc in chklbStoredProcedures.Items)
                {
                    sprocs.Add(sproc.Name);
                }
            }
            else
            {
                foreach (StoredProcedure sproc in chklbStoredProcedures.CheckedItems)
                {
                    sprocs.Add(sproc.Name);
                }
            }
            return sprocs;
        }

        private List<string> GetTablesToScript()
        {
            List<string> tables = new List<string>();
            if (chkAllTables.Checked)
            {
                foreach (Table table in this.chklbTables.Items)
                {
                    tables.Add(table.Name);
                }
            }
            else
            {
                foreach (Table table in this.chklbTables.CheckedItems)
                {
                    tables.Add(table.Name);
                }
            }
            return tables;
        }

        private List<string> GetViewsToScript()
        {
            List<string> views = new List<string>();
            if (chkAllViews.Checked)
            {
                foreach (View view in this.chklbViews.Items)
                {
                    views.Add(view.Name);
                }
            }
            else
            {
                foreach (View view in this.chklbViews.CheckedItems)
                {
                    views.Add(view.Name);
                }
            }
            return views;
        }

        private void PopulateSprocs()
        {
            StoredProcedureCollection sprocs = _scriptingEngine.StoredProcedures;
            chklbStoredProcedures.Items.Clear();
            foreach (StoredProcedure sproc in sprocs)
            {
                if (sproc.IsSystemObject) continue;
                chklbStoredProcedures.Items.Add(sproc);
            }
        }

        private void PopulateTables()
        {
            TableCollection tables = _scriptingEngine.Tables;
            chklbTables.Items.Clear();
            foreach (Table table in tables)
            {
                if (table.IsSystemObject) continue;
                chklbTables.Items.Add(table);
            }
        }

        private void PopulateViews()
        {
            ViewCollection views = _scriptingEngine.Views;
            chklbViews.Items.Clear();
            foreach (View view in views)
            {
                if (view.IsSystemObject) continue;
                chklbViews.Items.Add(view);
            }
        }

        #endregion

        #region EVENT HANDLERS

        /// <summary>
        /// Delegate for ObjectScriptedDelegate
        /// </summary>
        private void ScriptingEngine_ObjectScripted()
        {
            if (progressBar1.InvokeRequired)
            {
                ObjectScriptedDelegate del = new ObjectScriptedDelegate(ScriptingEngine_ObjectScripted);
                Invoke(del);
                return;
            }

            progressBar1.Value += 1;

            if (progressBar1.Value == progressBar1.Maximum)
            {
                panel1.Enabled = true;
                Cursor = Cursors.Default;
                progressBar1.Value = 0;
                MessageBox.Show("Complete!");
            }
            Application.DoEvents();
        }

        private void btnGenerateScripts_Click(object sender, EventArgs e)
        {
            List<string> sprocs = GetSprocsToScript();
            List<string> tables = GetTablesToScript();
            List<string> views = GetViewsToScript();

            int count = tables.Count;
            count += sprocs.Count;
            count += views.Count;

            progressBar1.Value = 0;
            progressBar1.Minimum = 0;
            progressBar1.Maximum = count;

            Debug.Print("ProgressBar Max: {0}", count);

            try
            {
                Cursor = Cursors.WaitCursor;
                panel1.Enabled = false;

                _scriptingEngine.EnsureDirectoriesExist();

                _scriptingEngine.GenerateTableScripts(tables);
                _scriptingEngine.GenerateSprocScripts(sprocs);
                _scriptingEngine.GenerateViewScripts(views);

                _scriptingEngine.GenerateSchemaInstallationScript();

                if (chkGenTableInstall.Checked) _scriptingEngine.GenerateSchemaInstallationScript();

                if (chkGenSprocInstall.Checked)
                {
                    foreach (ScriptingGroup group in _scriptingLayout.Groups)
                    {
                        _scriptingEngine.GenerateDefaultInstallationScript(group.GroupName, _scriptingLayout.SprocsSubDirectory);
                    }
                }
                if (chkGenViewInstall.Checked)
                {
                    foreach (ScriptingGroup group in _scriptingLayout.Groups)
                    {
                        _scriptingEngine.GenerateDefaultInstallationScript(group.GroupName, _scriptingLayout.ViewsSubDirectory);
                    }
                }

                if (chkGenCreateDatabase.Checked) _scriptingEngine.GenerateCreateDatabaseScript();

            }
            catch (Exception ex)
            {
                panel1.Enabled = true;
                Cursor = Cursors.Default;
                MessageBox.Show(ex.Message);
            }
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            try
            {
                Enabled = false;
                Cursor = Cursors.WaitCursor;

                try
                {
                    _scriptingLayout = ScriptingLayout.Load();
                }
                catch (InvalidOperationException ex)
                {
                    MessageBox.Show(ex.Message);
                    _ignore = true;
                    tabControl1.SelectTab(1);
                    _ignore = false;
                    txtOutputDirectory.Select();
                    return;
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                    _scriptingLayout = ScriptingLayout.Create();
                }
                finally
                {
                    if (null == _scriptingLayout) _scriptingLayout = ScriptingLayout.Create();
                }

                _scriptingEngine = new DbScriptingEngine(txtServer.Text, txtDbName.Text, _scriptingLayout);

                txtOutputDirectory.Text = ScriptingLayout.OutputDirectory;
                txtSchemaSubDirectory.Text = _scriptingLayout.SchemaSubDirectory;
                txtSprocSubDirectory.Text = _scriptingLayout.SprocsSubDirectory;
                txtViewSubDirectory.Text = _scriptingLayout.ViewsSubDirectory;

                BuildLayoutTree();

                _scriptingEngine.ObjectScripted += ScriptingEngine_ObjectScripted;

                propertyGrid1.SelectedObject = _scriptingEngine.CreateScripterOptions;
                propertyGrid2.SelectedObject = _scriptingEngine.DropScripterOptions;

                PopulateSprocs();
                PopulateTables();
                PopulateViews();

                panel1.Enabled = true;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                Cursor = Cursors.Default;
                Enabled = true;
            }
        }

        private void chkAllSprocs_CheckedChanged(object sender, EventArgs e)
        {
            chklbStoredProcedures.Enabled = !chkAllSprocs.Checked;
        }

        private void chkAllTables_CheckedChanged(object sender, EventArgs e)
        {
            chklbTables.Enabled = !chkAllTables.Checked;
        }

        private void chkAllViews_CheckedChanged(object sender, EventArgs e)
        {
            chklbViews.Enabled = !chkAllViews.Checked;
        }

        private void btnAddGroup_Click(object sender, EventArgs e)
        {
            frmGroup dlg = new frmGroup(null);
            DialogResult result = dlg.ShowDialog(this);
            if (DialogResult.Cancel == result) return;

            if (_scriptingLayout.GroupExists(dlg.GroupName))
            {
                MessageBox.Show(string.Format("There is already a Group named '{0}'.", dlg.GroupName));
                return;
            }
           
            _scriptingLayout.AddNewGroup(dlg.GroupName);
            BuildLayoutTree();
        }

        private void btnEditGroup_Click(object sender, EventArgs e)
        {
            TreeNode node = GetSelectedGroupNode();
            if (null == node)
            {
                MessageBox.Show("No Group has been selected to Edit.");
                return;
            }

            frmGroup dlg = new frmGroup(node.Text);
            DialogResult result = dlg.ShowDialog(this);
            if (DialogResult.Cancel == result) return;

            if (_scriptingLayout.GroupExists(dlg.GroupName))
            {
                MessageBox.Show(string.Format("There is already a Group named '{0}'.", dlg.GroupName));
                return;
            }

            _scriptingLayout.RenameGroup(node.Text, dlg.GroupName);
            BuildLayoutTree();
        }

        private void btnDeleteGroup_Click(object sender, EventArgs e)
        {
            TreeNode node = GetSelectedGroupNode();
            if (null == node)
            {
                MessageBox.Show("No Group has been selected to Delete.");
                return;
            }

            DialogResult result = MessageBox.Show(string.Format("Delete '{0}'", node.Text), "Question...", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (DialogResult.No == result) return;

            _scriptingLayout.DeleteGroup(node.Text);
            BuildLayoutTree();
        }

        private TreeNode GetSelectedGroupNode()
        {
            TreeNode selectedNode = tvLayout.SelectedNode;
            
            // TODO: get rid of this crap, and get the 
            // current group in scope, no matter what level
            if (null == selectedNode 
                || null == selectedNode.Parent 
                || selectedNode.Parent != tvLayout.Nodes[0]
                || selectedNode.Text == UNASSIGNED_NODE_TAG)
            {
                return null;
            }
            return selectedNode;
        }

        private TreeNode GetSelectedItemNode()
        {
            TreeNode selectedNode = tvLayout.SelectedNode;

            // TODO: get rid of this crap, and get the 
            // current group in scope, no matter what level
            if (null == selectedNode 
                || null == selectedNode.Parent 
                || selectedNode.Tag != null
                || selectedNode.Text == UNASSIGNED_NODE_TAG)
            {
                return null;
            }
            return selectedNode;
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            ScriptingLayout.OutputDirectory = txtOutputDirectory.Text;
            _scriptingLayout.SchemaSubDirectory = txtSchemaSubDirectory.Text;
            _scriptingLayout.SprocsSubDirectory = txtSprocSubDirectory.Text;
            _scriptingLayout.ViewsSubDirectory = txtViewSubDirectory.Text;

            try
            {
                ScriptingLayout.Save(_scriptingLayout);
                BuildLayoutTree();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void btnMoveUp_Click(object sender, EventArgs e)
        {
            TreeNode node = GetSelectedItemNode();
            if (null == node)
            {
                MessageBox.Show("Select an item to Move first.");
                return;
            }

            ScriptingLayout.MoveDelegate Moved = (ScriptingLayout.MoveDelegate)node.Parent.Tag;
            if (!Moved(node.Text, node.Parent.Parent.Text, true))
            {
                MessageBox.Show(string.Format("Unable to move '{0}' up.", node.Text));
                return;
            }
            BuildLayoutTree(node.Text);
        }

        private void btnMoveDown_Click(object sender, EventArgs e)
        {
            TreeNode node = GetSelectedItemNode();
            if (null == node)
            {
                MessageBox.Show("Select an item to Move first.");
                return;
            }

            ScriptingLayout.MoveDelegate Moved = (ScriptingLayout.MoveDelegate)node.Parent.Tag;
            if (!Moved(node.Text, node.Parent.Parent.Text, false))
            {
                MessageBox.Show(string.Format("Unable to move '{0}' down.", node.Text));
                return;
            }
            BuildLayoutTree(node.Text);
        }

        private void btnBrowse_Click(object sender, EventArgs e)
        {
            DialogResult result = folderBrowserDialog1.ShowDialog(this);
            if (DialogResult.Cancel == result) return;

            ScriptingLayout.OutputDirectory = folderBrowserDialog1.SelectedPath;
            txtOutputDirectory.Text = folderBrowserDialog1.SelectedPath;

            btnConnect_Click(null, null);
        }

        private void tabControl_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!_ignore && 1 == tabControl1.SelectedIndex && null == _scriptingEngine)
            {
                MessageBox.Show("You must connect to a database first.");
                tabControl1.SelectTab(0);
            }
        }

        private void frmScriptor_Closing(object sender, FormClosingEventArgs e)
        {
            if (null != _scriptingLayout && _scriptingLayout.IsDirty)
            {
                DialogResult result = MessageBox.Show("You have made changes the Scripting Layout.\nWould you like to save the changes?", "Layout Change", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
                switch (result)
                {
                    case DialogResult.Cancel:
                        e.Cancel = true;
                        break;
                    case DialogResult.Yes:
                        try
                        {
                            ScriptingLayout.Save(_scriptingLayout);                            
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message);
                            e.Cancel = true;
                        }
                        break;
                    case DialogResult.No:
                        break;
                }
            }
        }

        private void tvLayout_ItemDrag(object sender, ItemDragEventArgs e)
        {
            TreeNode node = (TreeNode)e.Item;

            //check if we are grabbing a root node.
            if (node.Text == _scriptingLayout.SchemaSubDirectory || node.Text == _DEFAULT_OUTPUT_DIR || node.Text == _scriptingLayout.SprocsSubDirectory || node.Text == _scriptingLayout.ViewsSubDirectory)
            {
                return;
            }

            //we will use this later on the drop
            if (_scriptingEngine.IsSproc(node.Text))
            {
                _mt = MoveType.Sproc;
            }
            else if (_scriptingEngine.IsTable(node.Text))
            {
                _mt = MoveType.Table;
            }
            else if (_scriptingEngine.IsTable(node.Text))
            {
                _mt = MoveType.View;
            }

            _nodeToMove = node;
            _groupToMoveFrom = node.Parent.Parent;            
            
            DoDragDrop(e.Item, DragDropEffects.Move);
        }

        private void tvLayout_DragDrop(object sender, DragEventArgs e)
        {           
            string newGroupName;
                      
            Point destinationPoint = tvLayout.PointToClient(new Point(e.X,e.Y));
            TreeNode destinationNode = tvLayout.GetNodeAt(destinationPoint);

            //need to determine if we are dropping on a group, subgroup (schema/table) or node of a group

            if (_scriptingLayout.GroupExists(destinationNode.Text))
            {
                //we landed on a group node, first check if it is same as previous
                if (destinationNode.Text == _groupToMoveFrom.Text)
                {
                    //we are in same node, so return
                    return;
                }
                
                newGroupName = destinationNode.Text;
            }
            else if(destinationNode.Text == _scriptingLayout.SchemaSubDirectory || destinationNode.Text == _scriptingLayout.SprocsSubDirectory || destinationNode.Text == _scriptingLayout.ViewsSubDirectory)
            {
                //we landed on a table, sproc or view node
                newGroupName = destinationNode.Parent.Text;
            }
            else
            {
                //we landed on a node that is inside a subgroup, we need to get the parent of the parent.
                newGroupName = destinationNode.Parent.Parent.Text;
            }

            bool result;

            switch (_mt)
            {
                case(MoveType.Sproc):
                    result = _scriptingLayout.MoveSprocToNewGroup(_nodeToMove.Text, _groupToMoveFrom.Text, newGroupName);
                    break;
                case(MoveType.Table):
                    result = _scriptingLayout.MoveTableToNewGroup(_nodeToMove.Text, _groupToMoveFrom.Text, newGroupName);
                    break;
                case(MoveType.View):
                    result = _scriptingLayout.MoveViewToNewGroup(_nodeToMove.Text, _groupToMoveFrom.Text, newGroupName);
                    break;
            }
            
            BuildLayoutTree(_nodeToMove.Text);
        }

        private void tvLayout_DragEnter(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }

        private void tvLayout_DragOver(object sender, DragEventArgs e)
        {
            // See where the cursor is
            Point pt = tvLayout.PointToClient(Cursor.Position);
            TreeNode hoverNode = tvLayout.GetNodeAt(pt);
            if (null != hoverNode)
            {
                tvLayout.SelectedNode = hoverNode;                
            }

            //See if we need to scroll up or down
            if ((pt.Y + SCROLL_INTERVAL) > tvLayout.Height)
            {
                //scroll down
                SendMessage(tvLayout.Handle, (int)277, (int)1, 0);
            }
            else if (pt.Y < (tvLayout.Top + SCROLL_INTERVAL))
            {
                // Call thje API to scroll up
                SendMessage(tvLayout.Handle, (int)277, (int)0, 0);
            }
        }

        private void cmTreeview_Opening(object sender, CancelEventArgs e)
        {

        }

        private void menuChild_Click(object sender, EventArgs e)
        {
            MenuItem menu = (MenuItem)sender;

            string groupName = menu.Text;
            TreeNode nodeToMove = menu.Tag as TreeNode;

            switch (nodeToMove.Parent.Text)
            {
                case ("Stored Procedures"):
                    _scriptingLayout.MoveSprocToNewGroup(nodeToMove.Text, 
                        nodeToMove.Parent.Parent.Text, groupName);
                    break;
                case("Views"):
                    _scriptingLayout.MoveViewToNewGroup(nodeToMove.Text,
                        nodeToMove.Parent.Parent.Text, groupName);
                    break;
                case("Schema"):
                    _scriptingLayout.MoveTableToNewGroup(nodeToMove.Text, 
                        nodeToMove.Parent.Parent.Text, groupName);
                    break;
            }
            BuildLayoutTree(nodeToMove.Text);
        }

        private void tvLayout_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                TreeViewHitTestInfo hitTest = tvLayout.HitTest(e.X, e.Y);
                
                //check if we are grabbing a root node.
                if (hitTest.Node.Text == _scriptingLayout.SchemaSubDirectory || hitTest.Node.Text == _DEFAULT_OUTPUT_DIR || hitTest.Node.Text == _scriptingLayout.SprocsSubDirectory || hitTest.Node.Text == _scriptingLayout.ViewsSubDirectory)
                {
                    return;
                }

                ContextMenu root = new ContextMenu();
                TreeNode node = tvLayout.Nodes[0];
                BuildMenu(hitTest.Node, node, ref root);
                root.Show(tvLayout,new Point(e.X, e.Y));
            }
        }

        #endregion

    } // class frmScriptor 

} // namespace OneEightySquaredDbScripter

